namespace SPDeploy.Library.Processing.Processors
{
    using System;
    using System.Collections.ObjectModel;
    using Data;
    using Exceptions;
    using Helpers;
    using Library;
    using Microsoft.SharePoint.Administration;

    public class SolutionDeploymentProcessor : ProcessorBase<SolutionDeployment, SPSolution>
    {
        #region Properties: public

        [ProcessorSetting(DefaultValue = IgnorableBehaviors.Ignore)]
        public IgnorableBehaviors AlreadyDeployed { get; set; }

        [ProcessorSetting(DefaultValue = IgnorableBehaviors.Ignore)]
        public IgnorableBehaviors AlreadyRetracted { get; set; }

        public override string Name
        {
            get { return "SolutionDeployment"; }
        }

        #endregion

        #region Methods: public

        public override SPSolution Install(ProcessorContext ctx, SolutionDeployment config)
        {
            Log(LogSeverity.Step, "Deploying solution '{0}'", config.Name);
            SPWebApplication webApp = GetParentApplication(config, ctx);
            Log(LogSeverity.SubStep, "Target web application is '{0}'", webApp.Name.IsEmpty() ? webApp.DefaultServerComment : webApp.Name);

            if (!DoesFarmContainSolutionWithName(webApp.Farm, config.Name))
            {
                throw new DeploymentTargetConfigException("A solution with the name '" + config.Name + "' could not be found in the solution store");
            }

            SPSolution solution = LookupSolution(webApp.Farm, config.Name);

            if (solution.DeployedWebApplications.Contains(webApp))
            {
                if (AlreadyDeployed == IgnorableBehaviors.Ignore)
                {
                    Log(LogSeverity.SubStep, "Already deployed to the application - config instructed ignoring it");
                    ctx.ChildrenDeleted = true;
                    return null;
                }
                else
                {
                    throw new DeploymentTargetConfigException("A solution with the name '" + config.Name + "' has already been deployed to web application " + webApp.Name);
                }
            }

            DeploySolution(config, solution, webApp);
            return solution;
        }

        public override SPSolution Uninstall(ProcessorContext ctx, SolutionDeployment config)
        {
            Log(LogSeverity.Step, "Retracting solution '{0}'", config.Name);
            SPWebApplication webApp = GetParentApplication(config, ctx);
            Log(LogSeverity.SubStep, "Target web application is '{0}'", webApp.Name.IsEmpty() ? webApp.DefaultServerComment : webApp.Name);

            if (!DoesFarmContainSolutionWithName(webApp.Farm, config.Name))
            {
                if (AlreadyRetracted == IgnorableBehaviors.Ignore)
                {
                    Log(LogSeverity.SubStep, "Could not be found in the solution store - config instructed ignoring it");
                    ctx.ChildrenDeleted = true;
                    return null;
                }
                else
                {
                    throw new DeploymentTargetConfigException("A solution with the name '" + config.Name + "' could not be found in the solution store");
                }
            }

            RetractSolution(config, ctx, webApp);
            return null;
        }

        public override SPSolution Update(ProcessorContext ctx, SolutionDeployment oldConfig, SolutionDeployment newConfig)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Methods: private

        private void DeploySolution(SolutionDeployment config, SPSolution solution, SPWebApplication webApp)
        {
            solution.Deploy(DateTime.Now, config.GlobalInstallWPPackDlls, new Collection<SPWebApplication>(new[] {webApp}), true);
            RunAdministrativeTimerJobs();
            AdminServiceTools.RecycleOwssvr();
        }

        private static bool DoesFarmContainSolutionWithName(SPFarm farm, string name)
        {
            return LookupSolution(farm, name) != null;
        }

        private static SPWebApplication GetParentApplication(SolutionDeployment config, ProcessorContext ctx)
        {
            var webApp = ctx.ParentResult as SPWebApplication;
            if (webApp == null)
            {
                throw new DeploymentTargetProcessorException("SolutionDeployment processor (" + config.Name + ") did not receive a valid web application instance");
            }
            return webApp;
        }

        private static SPSolution LookupSolution(SPFarm farm, string name)
        {
            return farm.Solutions[name];
        }

        private void RetractSolution(SolutionDeployment config, ProcessorContext ctx, SPWebApplication webApp)
        {
            SPSolution solution = LookupSolution(webApp.Farm, config.Name);
            solution.Retract(DateTime.Now, new Collection<SPWebApplication>(new[] {webApp}));
            RunAdministrativeTimerJobs();
            ctx.ChildrenDeleted = true;
        }

        private static void RunAdministrativeTimerJobs()
        {
            AdminServiceTools.AdmJobsStatus status = AdminServiceTools.ExecAdmSvcJobs();
            int retries = 5;
            while (retries-- > 0 && status == AdminServiceTools.AdmJobsStatus.TryAgain)
            {
                status = AdminServiceTools.ExecAdmSvcJobs();
            }
            if (status != AdminServiceTools.AdmJobsStatus.Success)
            {
                throw new DeploymentTargetProcessorException("SolutionDeployment processor failed to execute solution deployment timer jobs");
            }
        }

        #endregion
    }
}